package cn.magictodo.weblog.admin.service.impl;

import cn.magictodo.weblog.admin.convert.ArticleDetailConvert;
import cn.magictodo.weblog.admin.event.DeleteArticleEvent;
import cn.magictodo.weblog.admin.event.PublishArticleEvent;
import cn.magictodo.weblog.admin.event.ReadArticleEvent;
import cn.magictodo.weblog.admin.event.UpdateArticleEvent;
import cn.magictodo.weblog.admin.model.vo.article.*;
import cn.magictodo.weblog.admin.model.vo.tag.FindTagListRspVO;
import cn.magictodo.weblog.admin.service.AdminArticleService;
import cn.magictodo.weblog.common.domain.dos.*;
import cn.magictodo.weblog.common.domain.mapper.*;
import cn.magictodo.weblog.common.domain.vos.ArticleCategoryNameVO;
import cn.magictodo.weblog.common.enums.ResponseCodeEnum;
import cn.magictodo.weblog.common.exception.BizException;
import cn.magictodo.weblog.common.utils.PageResponse;
import cn.magictodo.weblog.common.utils.Response;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import static cn.magictodo.weblog.common.enums.RedisPrefixEnum.REDIS_IS_PUBLISH;

/**
 * AdminArticleServiceImpl
 *
 * @author liu
 * @since 2023-11-24 21:05
 */
@Service
@Slf4j
public class AdminArticleServiceImpl implements AdminArticleService {

    @Autowired
    private ArticleMapper articleMapper;
    @Autowired
    private ArticleContentMapper articleContentMapper;
    @Autowired
    private ArticleCategoryRelMapper articleCategoryRelMapper;
    @Autowired
    private CategoryMapper categoryMapper;
    @Autowired
    private TagMapper tagMapper;
    @Autowired
    private ArticleTagRelMapper articleTagRelMapper;
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    @Autowired
    private TransactionTemplate transactionTemplate;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    /**
     * 发布文章
     *
     * @param publishArticleReqVO
     * @return
     */
    @Override
    public Response publishArticle(PublishArticleReqVO publishArticleReqVO) { // 1. VO 转 ArticleDO, 并保存
        Long id = transactionTemplate.execute(status -> {
            ArticleDO articleDO = ArticleDO.builder()
                    .title(publishArticleReqVO.getTitle())
                    .cover(publishArticleReqVO.getCover())
                    .summary(publishArticleReqVO.getSummary())
                    .isPublish(publishArticleReqVO.getIsPublish())
                    .createTime(LocalDateTime.now())
                    .updateTime(LocalDateTime.now())
                    .build();

            articleMapper.insert(articleDO);

            // 拿到插入记录的主键 ID
            Long articleId = articleDO.getId();


            // 2. VO 转 ArticleContentDO，并保存
            ArticleContentDO articleContentDO = ArticleContentDO.builder()
                    .articleId(articleId)
                    .content(publishArticleReqVO.getContent())
                    .build();
            articleContentMapper.insert(articleContentDO);

            // 3. 处理文章关联的分类
            Long categoryId = publishArticleReqVO.getCategoryId();

            // 3.1 校验提交的分类是否真实存在
            CategoryDO categoryDO = categoryMapper.selectById(categoryId);
            if (Objects.isNull(categoryDO)) {
                log.warn("==> 分类不存在, categoryId: {}", categoryId);
                throw new BizException(ResponseCodeEnum.CATEGORY_NOT_EXISTED);
            }

            ArticleCategoryRelDO articleCategoryRelDO = ArticleCategoryRelDO.builder()
                    .articleId(articleId)
                    .categoryId(categoryId)
                    .build();
            articleCategoryRelMapper.insert(articleCategoryRelDO);

            // 4. 保存文章关联的标签集合
            List<String> publishTags = publishArticleReqVO.getTags();
            insertTags(articleId, publishTags);
            return articleId;
        });
//        如果文章发布，则发送文章发布的事件
        if (publishArticleReqVO.getIsPublish()) {
            // 发送文章发布事件
            log.info("==> 发布文章成功, id: {}", id);
            eventPublisher.publishEvent(new PublishArticleEvent(this, id));
        }


        return Response.success();

    }

    /**
     * 保存标签
     * @param articleId
     * @param publishTags
     */
    private void insertTags(Long articleId, List<String> publishTags) {
        // 筛选提交的标签（表中不存在的标签）
        List<String> notExistTags = null;
        // 筛选提交的标签（表中已存在的标签）
        List<String> existedTags = null;

        // 查询出所有标签
        List<TagDO> tagDOS = tagMapper.selectList(null);

        // 如果表中还没有添加任何标签
        if (CollectionUtils.isEmpty(tagDOS)) {
            notExistTags = publishTags;
        } else {
            List<String> tagIds = tagDOS.stream().map(tagDO -> String.valueOf(tagDO.getId())).collect(Collectors.toList());
            // 表中已添加相关标签，则需要筛选
            // 通过标签 ID 来筛选，包含对应 ID 则表示提交的标签是表中存在的
            existedTags = publishTags.stream().filter(publishTag -> tagIds.contains(publishTag)).collect(Collectors.toList());
            // 否则则是不存在的
            notExistTags = publishTags.stream().filter(publishTag -> !tagIds.contains(publishTag)).collect(Collectors.toList());

            // 补充逻辑：
            // 还有一种可能：按字符串名称提交上来的标签，也有可能是表中已存在的，比如表中已经有了 Java 标签，用户提交了个 java 小写的标签，需要内部装换为 Java 标签
            Map<String, Long> tagNameIdMap = tagDOS.stream().collect(Collectors.toMap(tagDO -> tagDO.getName().toLowerCase(), TagDO::getId));

            // 使用迭代器进行安全的删除操作
            Iterator<String> iterator = notExistTags.iterator();
            while (iterator.hasNext()) {
                String notExistTag = iterator.next();
                // 转小写, 若 Map 中相同的 key，则表示该新标签是重复标签
                if (tagNameIdMap.containsKey(notExistTag.toLowerCase())) {
                    // 从不存在的标签集合中清除
                    iterator.remove();
                    // 并将对应的 ID 添加到已存在的标签集合
                    existedTags.add(String.valueOf(tagNameIdMap.get(notExistTag.toLowerCase())));
                }
            }
        }

        // 将提交的上来的，已存在于表中的标签，文章-标签关联关系入库
        if (!CollectionUtils.isEmpty(existedTags)) {
            List<ArticleTagRelDO> articleTagRelDOS = Lists.newArrayList();
            existedTags.forEach(tagId -> {
                ArticleTagRelDO articleTagRelDO = ArticleTagRelDO.builder()
                        .articleId(articleId)
                        .tagId(Long.valueOf(tagId))
                        .build();
                articleTagRelDOS.add(articleTagRelDO);
            });
            // 批量插入
            articleTagRelMapper.insertBatchSomeColumn(articleTagRelDOS);
        }

        // 将提交的上来的，不存在于表中的标签，入库保存
        if (!CollectionUtils.isEmpty(notExistTags)) {
            // 需要先将标签入库，拿到对应标签 ID 后，再把文章-标签关联关系入库
            List<ArticleTagRelDO> articleTagRelDOS = Lists.newArrayList();
            notExistTags.forEach(tagName -> {
                TagDO tagDO = TagDO.builder()
                        .name(tagName)
                        .createTime(LocalDateTime.now())
                        .updateTime(LocalDateTime.now())
                        .build();

                tagMapper.insert(tagDO);

                // 拿到保存的标签 ID
                Long tagId = tagDO.getId();

                // 文章-标签关联关系
                ArticleTagRelDO articleTagRelDO = ArticleTagRelDO.builder()
                        .articleId(articleId)
                        .tagId(tagId)
                        .build();
                articleTagRelDOS.add(articleTagRelDO);
            });
            // 批量插入
            articleTagRelMapper.insertBatchSomeColumn(articleTagRelDOS);
        }
    }

    /**
     * 删除文章
     *
     * @param deleteArticleReqVO
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Response deleteArticle(DeleteArticleReqVO deleteArticleReqVO) {
        Long articleId = deleteArticleReqVO.getId();

        // 1. 删除文章
        articleMapper.deleteById(articleId);

        // 2. 删除文章内容
        articleContentMapper.deleteByArticleId(articleId);

        // 3. 删除文章-分类关联记录
        articleCategoryRelMapper.deleteByArticleId(articleId);

        // 4. 删除文章-标签关联记录
        articleTagRelMapper.deleteByArticleId(articleId);
        // 发送文章删除事件
        eventPublisher.publishEvent(new DeleteArticleEvent(this, articleId));

        return Response.success();
    }

    /**
     * 查询文章分页数据
     *
     * @param findArticlePageListReqVO
     * @return
     */
    @Override
    public Response findArticlePageList(FindArticlePageListReqVO findArticlePageListReqVO) {
        // 获取当前页、以及每页需要展示的数据数量
        Long current = findArticlePageListReqVO.getCurrent();
        Long size = findArticlePageListReqVO.getSize();
        String title = findArticlePageListReqVO.getTitle();
        String categoryName = findArticlePageListReqVO.getCategoryName();
        LocalDate startDate = findArticlePageListReqVO.getStartDate();
        LocalDate endDate = findArticlePageListReqVO.getEndDate();
        Boolean isPublish = findArticlePageListReqVO.getIsPublish();
        Integer isTop = findArticlePageListReqVO.getIsTop();
        Integer type = findArticlePageListReqVO.getType();


        // 执行分页查询
        Page<ArticleCategoryNameVO> articleDOPage = articleMapper.selectPageList(current, size, categoryName,title, startDate, endDate,isPublish,isTop,type);

        List<ArticleCategoryNameVO> articleCategoryNameVOs = articleDOPage.getRecords();

        // DO 转 VO
        List<FindArticlePageListRspVO> vos = null;
        if (!CollectionUtils.isEmpty(articleCategoryNameVOs)) {
            vos = articleCategoryNameVOs.stream()
                    .map(articleCategoryNameVO -> FindArticlePageListRspVO.builder()
                            .id(articleCategoryNameVO.getId())
                            .title(articleCategoryNameVO.getTitle())
                            .categoryName(articleCategoryNameVO.getCategoryName())
                            .cover(articleCategoryNameVO.getCover())
                            .createTime(articleCategoryNameVO.getCreateTime())
                            .updateTime(articleCategoryNameVO.getUpdateTime())
                            .isPublish(articleCategoryNameVO.getIsPublish())
                            .isTop(articleCategoryNameVO.getWeight() > 0 )
                            .build())
                    .collect(Collectors.toList());
        }

        return PageResponse.success(articleDOPage, vos);
    }

    /**
     * 查询文章详情
     *
     * @param findArticleDetailReqVO
     * @return
     */
    @Override
    public Response findArticleDetail(FindArticleDetailReqVO findArticleDetailReqVO) {
        Long articleId = findArticleDetailReqVO.getId();

        ArticleDO articleDO = articleMapper.selectById(articleId);

        if (Objects.isNull(articleDO)) {
            log.warn("==> 查询的文章不存在，articleId: {}", articleId);
            throw new BizException(ResponseCodeEnum.ARTICLE_NOT_FOUND);
        }

        ArticleContentDO articleContentDO = articleContentMapper.selectByArticleId(articleId);

        // 所属分类
        ArticleCategoryRelDO articleCategoryRelDO = articleCategoryRelMapper.selectByArticleId(articleId);

        // 对应标签
        List<ArticleTagRelDO> articleTagRelDOS = articleTagRelMapper.selectByArticleId(articleId);
        // 获取对应标签 ID 集合
        List<Long> tagIds = articleTagRelDOS.stream().map(ArticleTagRelDO::getTagId).collect(Collectors.toList());

        // DO 转 VO
        FindArticleDetailRspVO vo = ArticleDetailConvert.INSTANCE.convertDO2VO(articleDO);
        vo.setContent(articleContentDO.getContent());
        vo.setCategoryId(articleCategoryRelDO.getCategoryId());
        vo.setTagIds(tagIds);

        return Response.success(vo);
    }

    /**
     * 更新文章
     *
     * @param updateArticleReqVO
     * @return
     */
    @Override
    public Response updateArticle(UpdateArticleReqVO updateArticleReqVO) {
        Long id = transactionTemplate.execute(status -> {
            Long articleId = updateArticleReqVO.getId();

            // 1. VO 转 ArticleDO, 并更新
            ArticleDO articleDO = ArticleDO.builder()
                    .id(articleId)
                    .title(updateArticleReqVO.getTitle())
                    .cover(updateArticleReqVO.getCover())
                    .summary(updateArticleReqVO.getSummary())
                    .updateTime(LocalDateTime.now())
                    .isPublish(updateArticleReqVO.getIsPublish())
                    .build();
            int count = articleMapper.updateById(articleDO);

            // 根据更新是否成功，来判断该文章是否存在
            if (count == 0) {
                log.warn("==> 该文章不存在, articleId: {}", articleId);
                throw new BizException(ResponseCodeEnum.ARTICLE_NOT_FOUND);
            }

            // 2. VO 转 ArticleContentDO，并更新
            ArticleContentDO articleContentDO = ArticleContentDO.builder()
                    .articleId(articleId)
                    .content(updateArticleReqVO.getContent())
                    .build();
            articleContentMapper.updateByArticleId(articleContentDO);


            // 3. 更新文章分类
            Long categoryId = updateArticleReqVO.getCategoryId();

            // 3.1 校验提交的分类是否真实存在
            CategoryDO categoryDO = categoryMapper.selectById(categoryId);
            if (Objects.isNull(categoryDO)) {
                log.warn("==> 分类不存在, categoryId: {}", categoryId);
                throw new BizException(ResponseCodeEnum.CATEGORY_NOT_EXISTED);
            }

            // 先删除该文章关联的分类记录，再插入新的关联关系
            articleCategoryRelMapper.deleteByArticleId(articleId);
            ArticleCategoryRelDO articleCategoryRelDO = ArticleCategoryRelDO.builder()
                    .articleId(articleId)
                    .categoryId(categoryId)
                    .build();
            articleCategoryRelMapper.insert(articleCategoryRelDO);

            // 4. 保存文章关联的标签集合
            // 先删除该文章对应的标签
            articleTagRelMapper.deleteByArticleId(articleId);
            List<String> publishTags = updateArticleReqVO.getTags();
            insertTags(articleId, publishTags);

            return articleId;
        });
        Boolean isPublish = updateArticleReqVO.getIsPublish();
        Boolean isPublished = redisTemplate.hasKey(REDIS_IS_PUBLISH.getPrefix() + id);
        if(isPublish && isPublished)
        {//5.如果文章已推送，现在也选择推送，更新文章索引
            eventPublisher.publishEvent(new UpdateArticleEvent(this, id));
        }else if(!isPublish && isPublished)
        {
//         如果文章已推送，现在选择不推送，删除文章索引
            eventPublisher.publishEvent(new DeleteArticleEvent(this, id));
        }else if(isPublish &&!isPublished)
        {
//         如果文章未推送，现在选择推送，添加文章索引
            eventPublisher.publishEvent(new PublishArticleEvent(this, id));
        }

        return Response.success();
    }

    /**
     * 预览文章详情
     *
     * @param previewArticleDetailReqVO
     * @return
     */
    @Override
    public Response previewArticleDetail(PreviewArticleDetailReqVO previewArticleDetailReqVO) {
        Long articleId = previewArticleDetailReqVO.getArticleId();

        ArticleDO articleDO = articleMapper.selectById(articleId);

        // 判断文章是否存在
        if (Objects.isNull(articleDO)) {
            log.warn("==> 该文章不存在, articleId: {}", articleId);
            throw new BizException(ResponseCodeEnum.ARTICLE_NOT_FOUND);
        }

        // 查询正文
        ArticleContentDO articleContentDO = articleContentMapper.selectByArticleId(articleId);
//        计算正文字数
        int totalWords  = 10000;

        String newTime;
        if(articleDO.getUpdateTime().isAfter(articleDO.getCreateTime())){
            newTime = "最后更新于\u0020"+articleDO.getUpdateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        }else {
            newTime = "发布于\u0020"+articleDO.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        }

        // DO 转 VO
        PreviewArticleDetailRspVO vo = PreviewArticleDetailRspVO.builder()
                .title(articleDO.getTitle())
                .newTime(newTime)
                .content(articleContentDO.getContent())
                .readNum(articleDO.getReadNum())
                .totalWords(totalWords)
                .readTime("10分钟DOME")
                .build();

        // 查询所属分类
        ArticleCategoryRelDO articleCategoryRelDO = articleCategoryRelMapper.selectByArticleId(articleId);
        CategoryDO categoryDO = categoryMapper.selectById(articleCategoryRelDO.getCategoryId());
        vo.setCategoryId(categoryDO.getId());
        vo.setCategoryName(categoryDO.getName());

        // 查询标签
        List<ArticleTagRelDO> articleTagRelDOS = articleTagRelMapper.selectByArticleId(articleId);
        List<Long> tagIds = articleTagRelDOS.stream().map(ArticleTagRelDO::getTagId).collect(Collectors.toList());
        List<TagDO> tagDOS = tagMapper.selectByIds(tagIds);

        // 标签 DO 转 VO
        List<FindTagListRspVO> tagVOS = tagDOS.stream()
                .map(tagDO -> FindTagListRspVO.builder().id(tagDO.getId()).name(tagDO.getName()).build())
                .collect(Collectors.toList());
        vo.setTags(tagVOS);

        // 上一篇
        ArticleDO preArticleDO = articleMapper.selectPreArticle(articleId);
        if (Objects.nonNull(preArticleDO)) {
            FindPreNextArticleRspVO preArticleVO = FindPreNextArticleRspVO.builder()
                    .articleId(preArticleDO.getId())
                    .articleTitle(preArticleDO.getTitle())
                    .build();
            vo.setPreArticle(preArticleVO);
        }

        // 下一篇
        ArticleDO nextArticleDO = articleMapper.selectNextArticle(articleId);
        if (Objects.nonNull(nextArticleDO)) {
            FindPreNextArticleRspVO nextArticleVO = FindPreNextArticleRspVO.builder()
                    .articleId(nextArticleDO.getId())
                    .articleTitle(nextArticleDO.getTitle())
                    .build();
            vo.setNextArticle(nextArticleVO);
        }

        // 发布文章阅读事件
        eventPublisher.publishEvent(new ReadArticleEvent(this, articleId));

        return Response.success(vo);
    }

    /**
     * 更新文章是否发布
     *
     * @param isPublishArticleReqVO
     * @return
     */
    @Override
    public Response updateArticleIsPublish(IsPublishArticleReqVO isPublishArticleReqVO) {

        Long id = transactionTemplate.execute(status -> {
//        先取出id
            Long articleId = isPublishArticleReqVO.getId();
//        修改文章是否推送
            int i = articleMapper.updateById(ArticleDO.builder()
                    .id(articleId)
                    .isPublish(isPublishArticleReqVO.getIsPublish())
                    .build());
            // 根据更新是否成功，来判断该文章是否存在
            if (i == 0) {
                log.warn("==> 该文章不存在, articleId: {}", articleId);
                throw new BizException(ResponseCodeEnum.ARTICLE_NOT_FOUND);
            }
            return articleId;
        });
        if (isPublishArticleReqVO.getIsPublish()) {
            // 发布文章事件
            eventPublisher.publishEvent(new PublishArticleEvent(this, id));
        } else {
            // 发布删除文章事件
            eventPublisher.publishEvent(new DeleteArticleEvent(this, id));
        }
        return Response.success();
    }

    @Override
    public Response updateArticleIsTop(UpdateArticleIsTopReqVO updateArticleIsTopReqVO) {
        Long articleId = updateArticleIsTopReqVO.getId();
        Boolean isTop = updateArticleIsTopReqVO.getIsTop();

        // 默认权重为 0
        Integer weight = 0;
        // 若设置为置顶
        if (isTop) {
            // 查询出表中最大的权重值
            ArticleDO articleDO = articleMapper.selectMaxWeight();
            Integer maxWeight = articleDO.getWeight();
            // 最大权重值加一
            weight = maxWeight + 1;
        }

        // 更新该篇文章的权重值
        articleMapper.updateById(ArticleDO.builder()
                .id(articleId)
                .weight(weight)
                .build());

        return Response.success();
    }
}


